<?php
//Service General Controller
//sample: http://localhost/jdiary/service.diary.store?method=post&output=jsonp&input=soap&jsonp=sth&postdata=postdatafield
class ServiceCore
{
    /**
     * Application Controller
     *
     * @var ApplicationController
     */
    protected $App;
    /**
     * Headers outputed by ServiceCore
     *
     * @var String
     */
    public $Headers;
    function __construct (ApplicationController $App=null)
    {
        if ($App)
        { 
        $this->App = $App;
        $App->LoadCustomSystemModule("lib.phpxml",".");
        $App->LoadCustomSystemModule("model.base.service",".");
        }
    }
    
    /**
     * Extracts input from service call
     *
     * @param Array $BaseParams
     * @param String $InputType
     * @return Array
     */
    private function ExtractInput (&$BaseParams, $InputType)
    {
        $InputParams = $this->FormatInput($BaseParams[data], $InputType);
        unset($BaseParams[data]);
        if (is_array($BaseParams) and is_array($InputParams))
            $Params = array_merge($BaseParams, $InputParams);
        elseif (is_array($BaseParams))
            $Params = $BaseParams;
        elseif (is_array($InputParams))
            $Params = $InputParams;
        return $Params;
    }
    /**
     * Calls a local service as a function (in matters of speed)
     *
     * @param String $ServiceExternalName
     * @param mixed $Params
     * @param String $InputType
     * @param String $OutputType
     * @return mixed
     */
    function Invoke ($ServiceExternalName, $Params = null, $InputType = "array", $OutputType = "array")
    {
        if (isset($Params[output]))
        {
            $OutputType = $Params[output];
            unset($Params[output]);
        }
        if (isset($Params[input]))
        {
            $InputType = $Params[input];
            unset($Params[input]);
        }
        return $this->Serve($ServiceExternalName, $Params, $InputType, $OutputType);
    }
    
    /**
     * Calls a service amongst this jFramework Distributions.
     * This function is blind, thus takes a lot of time to call all distributions and retrieve results.
     * State: Experimental
     *
     * @param String $ServiceTitle
     * @param Mixed $Params
     * @param String $InputType
     * @param String $OutputType
     * @return Mixed
     */
    function SummonService ($ServiceTitle, $Params = null, $InputType = "array", $OutputType = "array")
    {
        $DS = new DistributedServers($this->App);
        list (, $Res) = $this->InvokeService($ServiceTitle, $Params, $InputType, $OutputType);
        if (isset($Res["Error"]))
        {
            foreach ($DS->Servers() as $ServerName => $ServerUrl)
            {
                if ($ServerName == "this")
                    continue;
                $this->CallService($ServerUrl . "/service." . $ServiceTitle, $Params, $InputType, $OutputType);
                if (! isset($Res["Error"]))
                    break;
            }
        }
        if (isset($Res[Error]))
        {
            return array("Error" => "No such service found in any distributed servers!");
        }
        return $Res;
    }
    /**
    Wraps another service in this function
		_deprecated
    The other services might not be on the local server, Uses SERVICES_INTERMEDIATE_ENCODING for wrapping of service.
    @param ServiceURL Address of service
    @param BaseParams Basic parameters of calling a serivce
    @param InputType Input format of the data
	@param OutputType Output format of the data
	@return Mixed result of service call
     */
    
    /**
     * Calls a web service on any URL
     * Uses constant("SERVICES_INTERMEDIATE_ENCODING") for wrapping, which is SOAP by default
     * 
     * @param String $ServiceUrl
     * @param mixed $BaseParams
     * @param String $InputType
     * @param String $OutputType
     * @return Mixed
     */
    function CallService ($ServiceUrl, $BaseParams, $InputType, $OutputType)
    {
        //Evaluating Service Parameters
        $Params = $this->ExtractInput($BaseParams, $InputType);
        //Wrapping Serialize Input Data
        $PostArray = $Params;
        //Enumerating Service
        $PostArray[input] = j::Registry("jf/services/IntermediateEncoding");
        $PostArray[output] = j::Registry("jf/services/IntermediateEncoding");
        $ch = curl_init();
        $Options = array(CURLOPT_RETURNTRANSFER => true , // return web page
CURLOPT_HEADER => false , // don't return headers
CURLOPT_FOLLOWLOCATION => true , // follow redirects
CURLOPT_ENCODING => "" , // handle all encodings
CURLOPT_USERAGENT => WHOAMI , // who am i
CURLOPT_AUTOREFERER => true , // set referer on redirect
CURLOPT_CONNECTTIMEOUT => 2 , // timeout on connect
CURLOPT_TIMEOUT => 2 , // timeout on response
CURLOPT_MAXREDIRS => 20 , // stop after 10 redirects
CURLOPT_POST => 1 , // i am sending post data
CURLOPT_POSTFIELDS => $PostArray , // this are my post vars
CURLOPT_SSL_VERIFYHOST => 0 , // don't verify ssl
CURLOPT_SSL_VERIFYPEER => false , //
CURLOPT_VERBOSE => 1)//
;
        curl_setopt_array($ch, $Options);
        curl_setopt($ch, CURLOPT_URL, $ServiceUrl);
        $Result = curl_exec($ch);
        curl_close($ch);
        //Deserializing Result
        $Result = unserialize($Result);
        if (! $Result or ! is_array($Result))
        {
            $Result = array();
            $Result[Error] = "Service did not respond.";
        }
        //Add invoke parameters to the result if there's an error
        if (array_key_exists("Error", $Result))
        {
            $Result[Input] = $Params;
            $Result[Input][Service] = $ServiceUrl;
            $Result[Input][InputType] = $InputType;
            $Result[Input][OutputType] = $OutputType;
        }
        //Formatting Output
        $Result = $this->FormatOutput($Result, $OutputType, $BaseParams);
        return $Result;
    }
    /**
     * Serves a local service, i.e loads the class and runs the service with given parameters
     *
     * @param String $ServiceName
     * @param mixed $BaseParams
     * @param String $InputType
     * @param String $OutputType
     * @return Mixed false on failure
     */
    function Serve ($ServiceName, $BaseParams, $InputType, $OutputType)
    {
        $ServiceTitle = new jpModule2ClassName($ServiceName);
        $ServiceTitle = $ServiceTitle->__toString();
        $ReflexInput = $BaseParams["reflect"];
        unset($BaseParams["reflect"]);
        //Evaluating Service Parameters, HTTP Binding
        $Params = $this->ExtractInput($BaseParams, $InputType);
        //Enumerating Service
        if (!$this->App->LoadSystemModule($ServiceName,true))
        {
            if (!$this->App->LoadModule($ServiceName,true))
                return false;
        }
        if (class_exists($ServiceTitle))
        {
            $ServiceObject = new $ServiceTitle($this->App);
            $Result = $ServiceObject->Execute($Params);
        }
        else
            $Result[Error] = "Service not found.";
        if (! $Result or ! is_array($Result))
        {
            $Result = array();
            $Result[Error] = "Service did not respond.";
        }
        //Add invoke parameters to the result if there's an error
        if ($ReflexInput != "false")
        {
            $Result[Input] = $Params;
            $Result[Input][Service] = $ServiceName;
            $Result[Input][InputType] = $InputType;
            $Result[Input][OutputType] = $OutputType;
        }
        //Formatting Output
        $Result = $this->FormatOutput($Result, $OutputType, $BaseParams);
        return $Result;
    }
    /**
     * Converts input data into associative array to be used by service class
     *
     * @param Mixed $Data
     * @param String $Type
     * @return Array
     */
    private function FormatInput ($Data, $Type)
    {
        $this->App->LoadSystemModule("model.services.input.base"); //base output
        $this->App->LoadSystemModule("model.services.input.$Type");
        $ClassName = "ServiceInput_$Type";
        if (class_exists($ClassName))
        {
            $InputObject = new $ClassName($this->App);
            $Result = $InputObject->Format($Data);
        }
        else
            $Result[Error][InputFormat] = "Input format not applicable.";
        return $Result;
    }
    /**
     * Convets associative array into desired format
     *
     * @param Array $Data
     * @param String $Type
     * @param Array $Request The whole request parameters which for JSONP calls, has $Request['jsonp'] as callback
     * @return String
     */
    private function FormatOutput ($Data, $Type, $Request)
    {
        $this->App->LoadSystemModule("model.services.output.base"); //base output
        $this->App->LoadSystemModule("model.services.output.$Type");
        $ClassName = "ServiceOutput_$Type";
        if (class_exists($ClassName))
        {
            $OutputObject = new $ClassName($this->App);
            $Result = $OutputObject->Format($Data, $Request);
        }
        else
            $Result = "Output format not applicable.";
        $this->Headers = $OutputObject->Headers;
        return $Result;
    }
}
?>
